/**********************************************************************

  Audacity: A Digital Audio Editor

  Mix.h

  Dominic Mazzoni
  Markus Meyer

***********************************************************************/

#ifndef __AUDACITY_MIX__
#define __AUDACITY_MIX__

#include "AudioGraphBuffers.h"
#include "AudioGraphSource.h"
#include "MixerOptions.h"
#include "SampleFormat.h"
#include <optional>

#include "DownmixStage.h"

class sampleCount;
class BoundedEnvelope;
class EffectStage;
class MixerSource;
class TrackList;
class WideSampleSequence;

class MIXER_API Mixer final
{
public:
    using WarpOptions = MixerOptions::Warp;
    using MixerSpec = MixerOptions::Downmix;
    using ResampleParameters = MixerOptions::ResampleParameters;
    using TimesAndSpeed = MixerOptions::TimesAndSpeed;
    using Stages = std::vector<MixerOptions::StageSpecification>;

    struct Input {
        Input(
            std::shared_ptr<const WideSampleSequence> pSequence = {},
            Stages stages = {})
            : pSequence{std::move(pSequence)}, stages{std::move(stages)}
        {}

        std::shared_ptr<const WideSampleSequence> pSequence;
        Stages stages;
    };
    using Inputs = std::vector<Input>;

    using ApplyVolume = DownmixStage::ApplyVolume;

    //
    // Constructor / Destructor
    //

    /*!
     * When creating with master effects stages `applyGain` is ignored
     @pre all sequences in `inputs` are non-null
     @pre !!masterEffects && mixerSpec == nullptr && numOutChannels <= 2
     @post `BufferSize() <= outBufferSize` (equality when no inputs have stages)
     */
    Mixer(
        Inputs inputs, std::optional<Stages> masterEffects, bool mayThrow, const WarpOptions& warpOptions, double startTime,
        double stopTime, unsigned numOutChannels, size_t outBufferSize, bool outInterleaved, double outRate, sampleFormat outFormat,
        bool highQuality = true,
        //! Null or else must have a lifetime enclosing this object's
        MixerSpec* mixerSpec = nullptr, ApplyVolume applyVolume = ApplyVolume::MapChannels);

    Mixer(const Mixer&) = delete;
    Mixer(Mixer&&) noexcept = delete;
    Mixer& operator=(const Mixer&) = delete;
    Mixer& operator=(Mixer&&) noexcept = delete;

    ~Mixer();

    size_t BufferSize() const { return mBufferSize; }

    //
    // Processing
    //

    //! Process a maximum of 'maxSamples' samples and put them into the buffer,
    //! at GetBuffer().
    /*!
     @pre `maxSamples <= BufferSize()`
     @post result: `result <= maxSamples`
     @return number of output samples, or 0, if there are no more samples that
     must be processed.
     */
    size_t Process(size_t maxSamples);

    /*!
     @post result: `result <= BufferSize()`
     */
    size_t Process() { return Process(BufferSize()); }

    //! Reposition processing to absolute time next time Process() is called.
    void Reposition(double t, bool bSkipping = false);

    //! Used in scrubbing and other nonuniform playback policies.
    void SetTimesAndSpeed(
        double t0, double t1, double speed, bool bSkipping = false);
    void SetSpeedForKeyboardScrubbing(double speed, double startTime);

    //! Current time in seconds (unwarped, i.e. always between startTime and stopTime)
    /*! This value is not accurate, it's useful for progress bars and indicators, but nothing else. */
    double MixGetCurrentTime();

    //! Retrieve the main buffer or the interleaved buffer
    constSamplePtr GetBuffer();

    //! Retrieve one of the non-interleaved buffers
    constSamplePtr GetBuffer(int channel);

    //! Deduce the effective width of the output, which may be narrower than the stored format
    sampleFormat EffectiveFormat() const;

private:

    void Clear();

    std::unique_ptr<EffectStage>& RegisterEffectStage(
        AudioGraph::Source& upstream, size_t numChannels, const MixerOptions::StageSpecification& stage, double outRate);

    // Input
    const unsigned mNumChannels;
    Inputs mInputs;
    const std::optional<Stages> mMasterEffects;

    // Transformations
    const size_t mBufferSize;

    std::pair<bool, sampleFormat>
    NeedsDither(bool needsDither, double rate) const;

private:

    // Output
    const ApplyVolume mApplyVolume;
    const bool mHighQuality;      // dithering
    const sampleFormat mFormat; // output format also influences dithering
    const bool mInterleaved;
    const double mOutRate;

    // INPUT
    sampleFormat mEffectiveFormat;
    bool mNeedsDither;
    bool mHasMixerSpec{ false };

    const std::shared_ptr<TimesAndSpeed> mTimesAndSpeed;

    // BUFFERS

    // Each channel's data is transformed, including application of
    // gains and pans, and then (maybe many-to-one) mixer specifications
    // determine where in mTemp it is accumulated
    AudioGraph::Buffers mTemp;

    // Final result applies dithering and interleaving
    const std::vector<SampleBuffer> mBuffer;

    std::vector<MixerSource> mSources;
    std::vector<EffectSettings> mSettings;
    std::vector<AudioGraph::Buffers> mStageBuffers;
    std::vector<std::unique_ptr<EffectStage> > mStages;
    std::unique_ptr<AudioGraph::Source> mDownmixStage;
    std::unique_ptr<AudioGraph::Source> mMasterDownmixStage;
    AudioGraph::Source* mDownstream{};
};
#endif
